#include "CPrimCylinder.h"
#include <math.h>


CPrimCylinder::CPrimCylinder(float radius, float height):IPrimitive(),
	m_radius(radius), m_height(height)
{}

CPrimCylinder::~CPrimCylinder(){}

bool CPrimCylinder::intersect(const CRay& ray, float& dist, bool useGlobalCoordinates)
{
	//change the coordinate system
	CRay R;
	if (useGlobalCoordinates)
		globalToLocal(ray, R);
	else
		convertRayToLocalCoordinates(ray, R);

	//calcul of delta
	float b = 2 * (R.getDirection().x * R.getOrigin().x + R.getDirection().z * R.getOrigin().z);
	float bSquared = b * b;

	float a = R.getDirection().x * R.getDirection().x + R.getDirection().z * R.getDirection().z;
	float c = R.getOrigin().x * R.getOrigin().x + R.getOrigin().z * R.getOrigin().z - m_radius * m_radius;

	float delta = bSquared - 4 * a * c;

	float t = 0;

	//if the ray hit the cylinder with two intersection
	if(delta > 0)
	{
		//calculate the solutions
		float sqrtDelta = sqrt(delta);
		float t1 = (-b + sqrtDelta)/(2*a);
		float t2 = (-b - sqrtDelta)/(2*a);
		
		int temp = (int)(t1 * 100);
		t1 = (float)temp / 100;
		temp = (int)(t2 * 100);
		t2 = (float)temp / 100;

		//calculate the z coordinates of the intersection point
		float heightT1 = R.getOrigin().y + t1 * R.getDirection().y;
		float heightT2 = R.getOrigin().y + t2 * R.getDirection().y;
	
		bool useEnds = false;
		if(isInside(ray.getOrigin()))
		{
			//top end
			if ((heightT1 > m_height && heightT2 < m_height && t1 > t2) ||
				(heightT2 > m_height && heightT1 < m_height && t2 > t1))
			{
				useEnds = true;
				t = calculateTTopEnd(R);
			}

			//bottom end
			else if((heightT1 > 0 && heightT2 < 0 && t1 < t2) || (heightT2 > 0 && heightT1 < 0 && t2 < t1))
			{
				useEnds = true;
				t = calculateTBottomEnd(R);
			}
		}
		else
		{
			//top end
			if ((heightT1 > m_height && heightT2 < m_height && t1 < t2) ||
				(heightT2 > m_height && heightT1 < m_height && t2 < t1))
			{
				useEnds = true;
				t = calculateTTopEnd(R);
			}

			//bottom end
			else if((heightT1 > 0 && heightT2 < 0 && t1 > t2) || (heightT2 > 0 && heightT1 < 0 && t2 > t1))
			{
				useEnds = true;
				t = calculateTBottomEnd(R);
			}
		}

		if(useEnds){}
		//body of the cylinder
		else if(isValideHeight(heightT1) || isValideHeight(heightT2))
		{
			if(isValideHeight(heightT1) && isValideHeight(heightT2))
			{
				if(t1 <= 0 && t2 <= 0)
					return false;
				else if(t1 <= 0)
					t = t2;
				else if(t2 <= 0)
					t = t1;
				else
					t = fmin(t1, t2);
			}
			else if(isValideHeight(heightT1))
				t = t1;
			else 
				t = t2;
		}
		//no intersection
		else return false;

		//set dist
		dist = t;

		//if dist negatif, no intersection
		if (dist< 0)
			return false;

		return true;

	}
	else if (delta == 0) // tangent
	{
		t = -b/(2 * a);
	}
	else //delta <0 so it's one of the ended. The ray is aligned with the height
	{
		float aligned = R.getOrigin().x  * R.getOrigin().x + R.getOrigin().z * R.getOrigin().z;

		//ray aligned with the cylinder
		if(aligned < m_radius*m_radius)
		{
			bool ret = calculateTFromHeight(R, &t);
			if(!ret) return ret;
		}
		else
			return false;
	}

	dist = t;
	return true;

}
void CPrimCylinder::computeNormal(const CRay& ray, float dist, CVector3f& normal)const
{
	//change the coordinate system
	CRay R;
	convertRayToLocalCoordinates(ray, R);

	CVector3f intersection = R.getOrigin() + R.getDirection() * dist;

	if(intersection.y < 0.001f && intersection.y > -0.001f)
		intersection.y = 0;

	if(intersection.y - 0.001f > 0 && intersection.y + 0.001f < m_height)
	{
		normal = intersection;
		normal.y = 0;
		normal.normalize();
	}
	else if(intersection.y <= 0)
		normal = CVector3f(0, -1, 0);
	else
		normal = CVector3f(0, 1, 0);

	//if ray from the inside
	if(R.getDirection().dot(normal) > 0)
		normal = normal * -1;

	//reset to the global coordinate systeme
	convertNormalToGlobalCoordinates(normal, normal);
}

bool CPrimCylinder::isInside(const CVector3f& p)const
{
	//convert p to local coordinates
	CVector3f localP;
	convertPointToLocalCoordinates(p, localP);

	//check if valid height
	if(!isValideHeight(localP.y))
		return false;

	//check if inside the circle
	if(localP.x * localP.x + localP.z * localP.z < m_radius * m_radius)
		return true;

	return false;
}

bool CPrimCylinder::isValideHeight(float h)const
{
	if(h >= 0 && h <= m_height)
		return true;

	return false;
}

bool CPrimCylinder::calculateTFromHeight(const CRay& ray, float* t)const
{
	float t1 = (-ray.getOrigin().y)/ray.getDirection().y;
	float t2 = (m_height - ray.getOrigin().y)/ray.getDirection().y;

	int temp = (int)(t2 * 100);
	t2 = (float)temp/100;
	temp = (int)(t1 * 100);
	t1 = (float)temp/100;

	if(t1 < t2)
	{
		if(t1 > 0)
			*t = t1;
		else if(t2 > 0)
			*t = t2;
		else
			return false;
	}
	else // t2<t1
	{
		if(t2 > 0)
			*t = t2;
		else if (t1> 0)
			*t = t1;
		else
			return false;
	}
	return true;
}

float CPrimCylinder::calculateTTopEnd(const CRay& R)const
{
	return (m_height - R.getOrigin().y)/R.getDirection().y;
}

float CPrimCylinder::calculateTBottomEnd(const CRay& R)const
{
	return (0 - R.getOrigin().y)/R.getDirection().y;
}

float CPrimCylinder::fmin(float f1, float f2)const
{
	if(f1 < f2)
		return f1;

	return f2;
}